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):
| Method | Lines | Responsibility |
|---|---|---|
submitTournamentScoreWithRetry() | ~129 | Retry logic for score submission |
setupTournamentSync() | ~48 | Firebase listener setup |
syncTournamentScoresManually() | ~43 | Manual sync trigger |
syncDetailedTournamentScores() | ~140 | Detailed end-by-end sync |
updateTournamentRoundStatus() | ~30 | Round status updates |
observeTournamentRoundState() | ~77 | Round state observation |
updateParticipantTournamentScore() | ~42 | Participant score updates |
updateTournamentParticipantProgressWithValues() | ~63 | Progress tracking |
syncTournamentScores() | ~18 | Basic 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? = nullTarget 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.ktinterface - ✅ 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:
- ✅ Copy method to TournamentSyncService
- ✅ Update references (StateFlows, dependencies)
- ✅ Add delegation in LiveScoringViewModel
- ✅ Test thoroughly (all tests pass)
- ✅ Delete original method from ViewModel
- ✅ 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:
- ✅ Created StateFlow in service
- ✅ Exposed via service interface
- ✅ Updated LiveScoringViewModel to expose service StateFlow
- ✅ UI uses new StateFlow references correctly
- ✅ Deleted old StateFlows from ViewModel
4. Listener Job Migration ✅
All 3 listener jobs successfully migrated:
- ✅ Moved job declarations to service
- ✅ Moved job initialization to service methods
- ✅ Added
cleanup()method to service - ✅ ViewModel calls
cleanup()inonCleared() - ✅ 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 ✅
-
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!)
-
Incremental Extraction - One method per day
- Manageable scope per extraction
- Clear progress tracking
- Easy to isolate issues
-
Comprehensive Testing - All tests passing
- Unit tests for service
- Integration tests for ViewModel
- Manual multi-device testing
What We’d Do Differently
-
Documentation - Keep extraction notes inline
- Document decisions as we go
- Track actual vs estimated effort
- Note edge cases discovered
-
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 ✅
- TournamentSyncService (556 lines) - Oct 5 ✅
- ScoreConflictResolutionService (262 lines) - Oct 2025 ✅
- EndCompletionService (400 lines) - Oct 6 ✅
Remaining Extractions ⏳
- TournamentRoundLifecycleService (~200 lines) - TODO
- 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:
- ✅
feat: Create TournamentSyncService interface and Hilt setup - ✅
refactor: Extract setupTournamentSync to TournamentSyncService - ✅
refactor: Extract syncTournamentScoresManually to TournamentSyncService - ✅
refactor: Extract syncDetailedTournamentScores to TournamentSyncService - ✅
refactor: Extract submitTournamentScoreWithRetry to TournamentSyncService - ✅
refactor: Extract round state observation to TournamentSyncService - ✅
test: Add TournamentSyncService unit tests - ✅
test: Update LiveScoringViewModel integration tests - ✅
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:
- LiveScoringVM-Analysis - Analysis that identified this extraction
- Implementation-Status-10-07-25 - Shows completion status (3/5 services done)
- Project-Journal-10-07-25 - Code deduplication follow-up work
- Checkpoint-Findings - Investigation that triggered this work
- Tech-Debt - Technical debt tracking
- Architecture - Overall architecture documentation