Back to course overview
Module 5Putting AI inside your app 12 min

Loading states and error handling

Wire the Reflect button to the model call with a spinner, a graceful error path, and a retry — the difference between a demo and a real feature.

A network call can be slow or fail. A demo ignores that; a shippable app handles it visibly. We'll model the feature's states explicitly so the UI is never a mystery to the user.

ReflectButton.swiftswift
import SwiftUI

struct ReflectButton: View {
    let entry: Entry

    enum Phase { case idle, loading, failed }
    @State private var phase: Phase = .idle

    var body: some View {
        switch phase {
        case .idle:
            Button("Reflect", systemImage: "sparkles") {
                Task { await reflect() }
            }
        case .loading:
            ProgressView("Reflecting…")
        case .failed:
            VStack(spacing: 8) {
                Text("Couldn't generate insights.")
                    .foregroundStyle(.secondary)
                Button("Retry") { Task { await reflect() } }
            }
        }
    }

    private func reflect() async {
        phase = .loading
        do {
            let insights = try await InsightsService.generate(
                for: entry.body, apiBase: AppConfig.apiBase
            )
            entry.aiTitle = insights.title
            entry.mood = insights.mood
            entry.reflection = insights.reflection
            phase = .idle
        } catch {
            phase = .failed
        }
    }
}

The Phase enum makes the three realities of a network feature — waiting, success, failure — impossible to forget. On success we write the results straight onto the Entry (SwiftData persists them); on failure we show a retry instead of a spinner that hangs forever.

Modeling state as an enum is a pro habit

Beginners scatter booleans (isLoading, hasError) that can contradict each other. A single enum with named cases can only ever be in one valid state. Reach for this any time a feature has more than two modes.

Tip

Ask your assistant to "add a timeout so the call fails after 20 seconds instead of hanging," and to "show the specific error message in a small caption when phase is .failed during development." Small resilience touches like these are what testers notice.