Concurrency in modern Swift with Async & Await in SwiftUI — Part 6

DevTechie Inc
Jul 22, 2023

Concurrency in modern Swift with Async & Await in SwiftUI — Part 6

In this article, we will continue our exploration of concurrency in modern Swift by building a simple version of an infinite scrolling list.

Here is what our final product will look like.

Let’s get started.

We will start by updating our data structure struct Coffee to conform to Equatable protocol.

Equatable types can be compared for value equality.

struct Coffee: Codable, Identifiable, Equatable {
    let id: Int
    let uid, blendName, origin, variety: String
    let notes, intensifier: String
    enum CodingKeys: String, CodingKey {
        case id, uid
        case blendName = "blend_name"
        case origin, variety, notes, intensifier
    }
}

We will update our service to turn into singleton so we don’t have to create multiple instances of the struct.

class WebService {
    
    private init() {}
    
    static let shared = WebService()
    
    func getCoffeeList() async throws -> [Coffee] {
        let (data, _) = try await URLSession
            .shared
            .data(from: URL(string: "https://random-data-api.com/api/coffee/random_coffee?size=10")!)
        return try JSONDecoder().decode([Coffee].self, from: data)
    }
}

We will attach task modifier to each row. We will compare each row’s coffee instance to the last instance of coffee object in array, if they are equal, we will call the web service for next 10 items.

struct DevTechieAsyncAwaitExample: View {
    @State private var coffees = [Coffee]()
    
    var body: some View {
        NavigationStack {
            VStack {
                if coffees.isEmpty {
                    ZStack {
                        Image(systemName: "heater.vertical.fill")
                            .font(.system(size: 60))
                            .rotationEffect(.degrees(-90))
                            .offset(y: -20)
                            .foregroundStyle(.gray.opacity(0.4).shadow(.inner(radius: 2)))
                        Image(systemName: "cup.and.saucer.fill")
                            .font(.system(size: 100))
                            .foregroundStyle(.gray.shadow(.inner(radius: 2)))
                    }
                    
                } else {
                    List(coffees) { coffee in
                        VStack(alignment: .leading, spacing: 5) {
                            Text(coffee.blendName)
                                .font(.title3)
                            Text("Notes: \(coffee.notes)")
                                .font(.subheadline)
                                .foregroundStyle(.secondary)
                            HStack {
                                Text("Origin: \(coffee.origin)")
                                Spacer()
                                Text("Variety: \(coffee.variety)")
                            }
                            .font(.caption)
                            .foregroundStyle(.tertiary)
                        }
                        .task {
                            if coffee == coffees.last {
                                do {
                                    coffees.append(contentsOf: try await WebService.shared.getCoffeeList())
                                } catch {
                                    print(error.localizedDescription)
                                }
                            }
                        }
                    }
                }
            }
            .task {
                await refreshData()
            }
            .navigationTitle("DevTechie")
        }
    }
    
    private func refreshData() async {
        do {
            coffees = try await WebService.shared.getCoffeeList()
        } catch {
            print(error.localizedDescription)
        }
    }
}

The complete code should look like this:

struct Coffee: Codable, Identifiable, Equatable {
    let id: Int
    let uid, blendName, origin, variety: String
    let notes, intensifier: String
    enum CodingKeys: String, CodingKey {
        case id, uid
        case blendName = "blend_name"
        case origin, variety, notes, intensifier
    }
}
class WebService {
    
    private init() {}
    
    static let shared = WebService()
    
    func getCoffeeList() async throws -> [Coffee] {
        let (data, _) = try await URLSession
            .shared
            .data(from: URL(string: "https://random-data-api.com/api/coffee/random_coffee?size=10")!)
        return try JSONDecoder().decode([Coffee].self, from: data)
    }
}
struct DevTechieAsyncAwaitExample: View {
    @State private var coffees = [Coffee]()
    
    var body: some View {
        NavigationStack {
            VStack {
                if coffees.isEmpty {
                    ZStack {
                        Image(systemName: "heater.vertical.fill")
                            .font(.system(size: 60))
                            .rotationEffect(.degrees(-90))
                            .offset(y: -20)
                            .foregroundStyle(.gray.opacity(0.4).shadow(.inner(radius: 2)))
                        Image(systemName: "cup.and.saucer.fill")
                            .font(.system(size: 100))
                            .foregroundStyle(.gray.shadow(.inner(radius: 2)))
                    }
                    
                } else {
                    List(coffees) { coffee in
                        VStack(alignment: .leading, spacing: 5) {
                            Text(coffee.blendName)
                                .font(.title3)
                            Text("Notes: \(coffee.notes)")
                                .font(.subheadline)
                                .foregroundStyle(.secondary)
                            HStack {
                                Text("Origin: \(coffee.origin)")
                                Spacer()
                                Text("Variety: \(coffee.variety)")
                            }
                            .font(.caption)
                            .foregroundStyle(.tertiary)
                        }
                        .task {
                            if coffee == coffees.last {
                                do {
                                    coffees.append(contentsOf: try await WebService.shared.getCoffeeList())
                                } catch {
                                    print(error.localizedDescription)
                                }
                            }
                        }
                    }
                }
            }
            .task {
                await refreshData()
            }
            .navigationTitle("DevTechie")
        }
    }
    
    private func refreshData() async {
        do {
            coffees = try await WebService.shared.getCoffeeList()
        } catch {
            print(error.localizedDescription)
        }
    }
}