DEV Community

Rob Mulder 
Rob Mulder 

Posted on

Building an unofficial Dumpert client for Apple TV with Swift 6 and SwiftUI


Dumpert is a Dutch video site I've watched for years, but there's never been an Apple TV app, so I built one. DumpertTV is an unofficial, open-source tvOS client. Here's how it's put together and a few things that were more interesting than I expected.

Disclaimer up front: this project is not affiliated with Dumpert or DPG Media B.V. It's an independent app that uses the public Dumpert API.

The stack

  • Swift 6 with strict concurrency (complete mode) across every target
  • SwiftUI for all UI, tvOS 18+
  • XcodeGen so the .xcodeproj is generated from a project.yml and never committed
  • CloudKit, GroupActivities (SharePlay), Vision, AVKit, Swift Testing

One actor for the network, one source of truth for the UI

The whole networking layer is an actor. That makes per-request state like ETags and retry bookkeeping thread-safe without a single lock:

actor DumpertAPIClient {
    private var etags: [URL: String] = [:]

    func fetch<T: Decodable>(_ endpoint: APIEndpoint) async throws -> T {
        // Exponential backoff on 5xx + network errors; honours 304 Not Modified.
        try await fetchWithRetry(endpoint, attempt: 0)
    }
}
Enter fullscreen mode Exit fullscreen mode

The UI reads from a single @Observable @MainActor repository injected through the SwiftUI environment — no Combine, no view models competing over the same state:

@Observable @MainActor
final class VideoRepository {
    private(set) var hotshiz: [MediaItem] = []
    let apiClient: APIClientProtocol   // protocol-backed for testing
}
Enter fullscreen mode Exit fullscreen mode
ContentView()
    .environment(videoRepository)
Enter fullscreen mode Exit fullscreen mode

Views just read repository.hotshiz; updates flow automatically. With Swift 6's strict concurrency on, the compiler kept me honest about every actor hop.

Things that were trickier than expected

  • tvOS focus + a top tab bar. Getting a Netflix-style hero carousel to behave with the focus engine took real care. It also made automating screenshots interesting — scripted remote input doesn't reliably drive the simulator, so I added #if DEBUG launch-argument hooks to jump straight to any tab/category for a capture script.
  • Face-centered thumbnails. Dumpert thumbnails are arbitrary crops, so I run Vision face detection and bias the crop toward detected faces. On a 55" screen the difference is night and day.
  • CloudKit delta sync with change tokens for watch progress, settings and search history — resume-where-you-left-off works across multiple Apple TVs.
  • Top Shelf extension that surfaces trending content on the home screen, sharing data with the app through an App Group.
  • SharePlay (GroupActivities) for synchronized "watch together" playback.

Testing

80+ unit tests with Swift Testing, covering API decoding, the CloudKit merge logic, search/filter state, and autoplay/up-next playlist navigation. Protocols (APIClientProtocol, CacheServiceProtocol) make the network and disk layers easy to mock.

Try it / read the source

Feedback and PRs welcome, especially from anyone doing focus-heavy tvOS SwiftUI. The hero-carousel focus work was the fiddliest part, and I'd love to compare notes.

Top comments (0)