Skip to main content

Documentation Index

Fetch the complete documentation index at: https://285e39fd5e337e58f16290.sightscreen.app/llms.txt

Use this file to discover all available pages before exploring further.

Core philosophy

The iOS app is a dumb view layer. The backend owns all formatting, string construction, and business logic. The app’s job is to receive pre-formatted data and render it.
Never compute display strings on the client. Values like "87/6", "RR: 8.50", or "Rohit Sharma 45 (32)" arrive ready-to-render from the API. If you find yourself building a display string in Swift, you are violating the architecture.

What the iOS app does NOT do

  • Format scores, overs, run rates, or player stats
  • Decide what to show based on match state (e.g., innings break vs. live)
  • Apply conditional logic to determine display layouts based on match phase
  • Transform raw data into user-facing text

What the iOS app DOES do

  • Render pre-formatted display states from the backend
  • Manage UI navigation and presentation
  • Handle device-level concerns (Live Activities, push tokens, permissions)
  • Cache and poll for data
  • Send user actions back to the backend

MVVM with @Observable and @Environment DI

The app uses MVVM powered by Swift’s @Observable macro (Observation framework), with dependency injection through @Environment.
LayerResponsibility
ViewSwiftUI views that read from observable models and render UI
ViewModel@Observable classes that hold view state and call services
ServiceProtocol-based services injected via @Environment, each with Real + Fake implementations
BackendSource of truth for all data and display formatting
// Accessing a service in a view
struct MatchListView: View {
    @Environment(\.scheduleService) private var scheduleService

    var body: some View {
        // ...
    }
}
// Defining the environment key
private struct ScheduleServiceKey: EnvironmentKey {
    static let defaultValue: ScheduleService = RealScheduleService()
}

extension EnvironmentValues {
    var scheduleService: ScheduleService {
        get { self[ScheduleServiceKey.self] }
        set { self[ScheduleServiceKey.self] = newValue }
    }
}
Every protocol has two implementations:
  • Real — hits the actual backend API
  • Fake — returns canned data for previews and testing
The app entry point injects the real implementations. SwiftUI previews and tests inject fakes.
ViewModels are @Observable classes, not ObservableObject with @Published. This uses the newer Observation framework available from iOS 17, with backcompat handled where needed.

Facade pattern: LiveActivityService

The most complex subsystem uses the facade pattern. LiveActivityService is a thin coordinator that composes three focused managers: Views and other services only interact with the facade. The three managers are internal implementation details.
ManagerSingle responsibility
TokenManagerPush-to-start + update token observation, deduplication, backend registration
LifecycleManagerActivity.request(), adopt push-started LAs, state observation, stale check, restore
FollowManagerFollow/unfollow API, local persistence, retry, bell icon state, sync from server
The facade prevents views from needing to know which manager handles what. A view calls liveActivityService.follow(matchId:) and the facade coordinates FollowManager + LifecycleManager + TokenManager behind the scenes.

DataSource + ViewSubscriber

A generic pattern for any data that needs to be fetched, cached, and optionally polled.

DataSource protocol

protocol DataSource<T> {
    associatedtype T

    var data: T? { get }

    func load() async throws -> T       // cache-first
    func refresh() async throws -> T    // force fetch
    func shouldPoll() -> Bool
    var pollIntervalMs: Int { get }
}

ViewSubscriber modifier

A SwiftUI view modifier that binds a DataSource to a view’s lifecycle.
MatchListView()
    .viewSubscriber(dataSource: liveMatchesDataSource)
BehaviorDetail
Lifecycle bindingonAppear starts polling, onDisappear stops
ScenePhase awarenessPauses in background, resumes on foreground
Cache-firstShows cached data immediately, refreshes in background

Concrete data sources

DataSourceBehavior
LiveMatchesDataSourceAlways polls. pollIntervalMs is server-driven via retryAfterMs in response
ScheduleDataSourcePer-date caching. Only polls for today’s date (historical dates are static)

Cache-first reads with stale checking

The app follows a cache-first strategy for all data:
  • MatchCacheStore indexes matches by date with TTL based on match state (live = short TTL, completed = long TTL)
  • ImageCacheService uses a two-tier cache (memory + disk in app group container)
  • ScheduleService uses a sliding-window cache for N days of schedule data
Cache-first means the UI never shows a loading spinner for data the user has already seen. Stale checks happen in the background and the UI updates reactively via @Observable.

Swift 6 actor isolation

The app uses Swift 6 strict concurrency. Key patterns:
PatternWhere used
@MainActorAll @Observable classes, ViewModels, and UI-bound state
Sendable conformanceAll model types (generated and hand-written)
nonisolated methodsPure computation and encoding/decoding that does not touch UI state
Structured concurrencyTask {} blocks scoped to view lifecycle via ViewSubscriber
All services are @Observable and implicitly @MainActor. If you need to do background work inside a service, use nonisolated methods or explicit Task.detached blocks. Never block the main actor with synchronous network calls.

Data flow

1

Backend returns pre-formatted data

The API responds with display-ready models — formatted strings, ordered arrays, resolved image URLs.
2

Service layer fetches and caches

Protocol-based services make the API call, decode the response, and cache results (via MatchCacheStore or service-local caches).
3

ViewModel exposes state

@Observable view models hold the decoded data. SwiftUI views automatically re-render when properties change.
4

Views render

SwiftUI views bind directly to view model properties. No transformation — just layout and styling.
5

User actions flow back

Taps, follows, and other interactions call service methods, which hit the backend API.

Tech constraints

ConstraintValue
UI frameworkSwiftUI only (no UIKit)
Minimum iOS version16.1
Reason for iOS 16.1Live Activities require ActivityKit, available from iOS 16.1
Concurrency modelSwift 6 strict concurrency
ArchitectureMVVM with @Observable
DI mechanism@Environment with protocol-based services
Data formatAll display data pre-formatted by backend
The iOS 16.1 minimum means you can use NavigationStack, @Observable (with backcompat), and the full ActivityKit API surface.