Utility Services Reference
Reference for utility and support services.
Overview
Utility services provide common functionality used throughout the application.
Services Covered:
- ValidationService
- DateTimeService
- LoggingService
- DatabaseMigrationService
- TournamentScoreCacheService
- ParticipantManagementService (Recommended)
- EquipmentValidationService
ValidationService
File: domain/services/ValidationService.kt
Purpose: General data validation
API
class ValidationService {
// Input validation
fun validateInput(text: String): Boolean
fun validateEmail(email: String): Boolean
fun validateLength(text: String, min: Int, max: Int): Boolean
// Score validation
fun validateArrowScore(score: Int): Boolean
fun validateArrowScores(scores: List<Int>): Boolean
// Entity validation
fun validateRound(round: Round): ValidationResult
fun validateBowSetup(setup: BowSetup): ValidationResult
}
data class ValidationResult(
val isValid: Boolean,
val errors: List<String> = emptyList()
)Usage
val result = validationService.validateRound(round)
if (!result.isValid) {
result.errors.forEach { error ->
showError(error)
}
return
}DateTimeService
File: domain/services/DateTimeService.kt
Purpose: Timestamp and date utilities
API
class DateTimeService {
// Current time
fun now(): Long = System.currentTimeMillis()
// Formatting
fun formatDate(timestamp: Long, pattern: String): String
fun formatTime(timestamp: Long): String
fun formatDateTime(timestamp: Long): String
// Parsing
fun parseDate(dateString: String, pattern: String): Long?
// Calculations
fun addDays(timestamp: Long, days: Int): Long
fun daysBetween(start: Long, end: Long): Int
fun isSameDay(timestamp1: Long, timestamp2: Long): Boolean
}Usage
val formattedDate = dateTimeService.formatDate(
round.date,
"MMM dd, yyyy"
) // "Nov 01, 2025"
val isToday = dateTimeService.isSameDay(round.date, dateTimeService.now())LoggingService
File: domain/services/LoggingService.kt
Purpose: Centralized logging
API
class LoggingService {
fun logDebug(tag: String, message: String)
fun logInfo(tag: String, message: String)
fun logWarning(tag: String, message: String, throwable: Throwable? = null)
fun logError(tag: String, message: String, throwable: Throwable)
// Analytics
fun logEvent(eventName: String, parameters: Map<String, Any>)
fun logScreenView(screenName: String)
}Usage
loggingService.logInfo("RoundRepository", "Creating round: ${round.name}")
try {
repository.save(data)
} catch (e: Exception) {
loggingService.logError("Repository", "Save failed", e)
}
loggingService.logEvent("round_completed", mapOf(
"roundId" to roundId,
"score" to totalScore
))DatabaseMigrationService
File: data/services/DatabaseMigrationService.kt
Purpose: Room database schema migrations
API
class DatabaseMigrationService {
// Migration management
fun getCurrentVersion(): Int
fun getTargetVersion(): Int
fun getMigrationsNeeded(): List<Migration>
// Execute migrations
fun executeMigrations(): Result<Unit>
fun executeMigration(from: Int, to: Int): Result<Unit>
// Rollback support
fun canRollback(): Boolean
fun rollbackMigration(): Result<Unit>
// Data preservation
fun backupDatabase(): Result<File>
fun restoreDatabase(backup: File): Result<Unit>
}Usage
val migrations = migrationService.getMigrationsNeeded()
if (migrations.isNotEmpty()) {
println("Need to run ${migrations.size} migrations")
migrationService.executeMigrations()
.onSuccess { println("Migrations complete") }
.onFailure { error ->
migrationService.rollbackMigration()
showError(error.message)
}
}TournamentScoreCacheService
File: data/services/TournamentScoreCacheService.kt
Purpose: Smart caching for tournament data
Features
- Status-aware TTL: Different cache durations based on tournament status
- 50-90% reduction in Firebase reads
- 5-10x faster load times
- Automatic cache maintenance
Cache Strategy
| Tournament Status | TTL | Rationale |
|---|---|---|
| COMPLETED | Indefinite | Immutable data |
| IN_PROGRESS | 5 minutes | Balance freshness vs reads |
| PLANNED | 10 minutes | Rarely changes |
API
class TournamentScoreCacheService {
// Get from cache or fetch
suspend fun getTournament(id: String): Result<Tournament>
// Cache operations
fun cacheScore(score: TournamentScore)
fun invalidateCache(tournamentId: String)
fun clearCache()
// Cache stats
fun getCacheHitRate(): Double
fun getCacheSizeBytes(): Long
}Usage
// Automatically uses cache
val tournament = cacheService.getTournament(tournamentId)
.getOrNull()
// Force refresh
cacheService.invalidateCache(tournamentId)
val fresh = cacheService.getTournament(tournamentId)ParticipantManagementService
File: domain/services/ParticipantManagementService.kt (Recommended)
Status: 📝 RECOMMENDED FOR EXTRACTION (~200 lines from LiveScoringViewModel)
Proposed API
class ParticipantManagementService(
private val repository: RoundRepository
) {
// Participant CRUD
suspend fun addParticipant(roundId: Long, participantId: Long): Result<Unit>
suspend fun removeParticipant(roundId: Long, participantId: Long): Result<Unit>
// Participant selection
suspend fun getParticipants(roundId: Long): Result<List<Participant>>
suspend fun getActiveParticipant(roundId: Long): Result<Participant?>
// Multi-participant logic
suspend fun switchActiveParticipant(
roundId: Long,
participantId: Long
): Result<Unit>
suspend fun getParticipantScore(
roundId: Long,
participantId: Long
): Result<Int>
suspend fun getRankings(roundId: Long): Result<List<ParticipantRanking>>
}See: Extraction Plan
EquipmentValidationService
File: domain/services/EquipmentValidationService.kt
Purpose: Equipment configuration validation
API
class EquipmentValidationService {
// Bow validation
fun validateBowSetup(setup: BowSetup): ValidationResult
// Arrow validation
fun validateArrowSetup(setup: ArrowSetup): ValidationResult
// Component validation
fun validateSightConfiguration(sight: SightConfiguration): ValidationResult
fun validateStabilizerConfiguration(stab: StabilizerConfiguration): ValidationResult
// Compatibility checks
fun checkBowArrowCompatibility(
bow: BowSetup,
arrows: ArrowSetup
): CompatibilityResult
}
data class CompatibilityResult(
val isCompatible: Boolean,
val warnings: List<String> = emptyList()
)Usage
val bowResult = validationService.validateBowSetup(bowSetup)
if (!bowResult.isValid) {
showErrors(bowResult.errors)
return
}
val compat = validationService.checkBowArrowCompatibility(bow, arrows)
if (!compat.isCompatible) {
showWarning("Bow and arrows may not be compatible")
}Best Practices
All Utility Services Should:
- Be Stateless - No mutable state
- Be Pure - Same input = same output (when possible)
- Be Reusable - Used across multiple features
- Be Testable - Easy to unit test
- Have Clear Purpose - Single responsibility
Example: Good Utility Service
// GOOD: Stateless, reusable
class DateTimeService {
fun formatDate(timestamp: Long, pattern: String): String {
// Pure function
}
}
// BAD: Stateful
class DateTimeService {
private var lastTimestamp: Long = 0 // ✗ State
fun formatDate(timestamp: Long): String {
lastTimestamp = timestamp // ✗ Side effect
// ...
}
}Related Documentation
Status: ✅ Core services in production, 1 recommended for extraction Pattern: Utility and support functionality Last Updated: 2025-11-01