Async Await with SwiftUI

In this article It has explanation how to use async/await with two network calls
We can use an array of Task instances to manage multiple concurrent tasks in Swift. This approach is useful for running multiple network requests in parallel and handling their results collectively. Here’s how you can incorporate an array of Task instances into your ViewModel for fetching data concurrently.

Step-by-Step Implementation with Task Array
  1. Create a Model for Your Data
  2. Create a Network Service
  3. Create a ViewModel with Task Array
  4. Create a SwiftUI View
1. Create a Model for Your Data

Create a new Swift file named Hotel.swift and define your model:

2. Create a Network Service

Create a new Swift file named NetworkService.swift for your network calls:

3. Create a ViewModel with Task Array

Create a new Swift file named HotelViewModel.swift for your view model:

4. Create a SwiftUI View

Finally, create your main SwiftUI view in ContentView.swift:

Explanation
  • Model: Hotel and AdditionalInfo are defined to match the structure of the JSON data.
  • NetworkService: Contains methods to fetch data from the provided URLs.
  • ViewModel: HotelViewModel manages the state, including loading and error states, and performs data fetching using an array of Task instances. It includes functionality to cancel ongoing tasks.
  • ContentView: Displays a loader, handles errors, and shows the list of hotels and additional info when available. The onDisappear modifier is used to cancel the fetch tasks if the view disappears.

This implementation ensures that multiple tasks can be managed and canceled effectively, making the app responsive to user interactions and preventing unnecessary network requests.

import SwiftUI
import Foundation

// MARK: - Model Definitions

struct Hotel: Codable, Identifiable {
    let id: Int
    let name: String
    let cuisine: String
}

struct AdditionalInfo: Codable {
    let title: String
}

// MARK: - Network Service

class NetworkService {
    static let shared = NetworkService()

    private init() {}

    // Asynchronously fetch hotels from a JSON endpoint
    func fetchHotels() async throws -> [Hotel] {
        let url = URL(string: "https://raw.githubusercontent.com/janeshsutharios/REST_GET_API/main/HotelsList.json")!
        let (data, _) = try await URLSession.shared.data(from: url)
        do  {
            let results = try JSONDecoder().decode([Hotel].self, from: data)
            return results
        } catch {
            print("Error in fetching hotels:", error)
            return []
        }
    }

    // Asynchronously fetch additional information from another JSON endpoint
    func fetchAdditionalInfo() async throws -> [AdditionalInfo] {
        let url = URL(string: "https://raw.githubusercontent.com/janeshsutharios/REST_GET_API/main/get.json")!
        let (data, _) = try await URLSession.shared.data(from: url)
        do {
            return try JSONDecoder().decode([AdditionalInfo].self, from: data)
        } catch {
            print("Error in fetching additional info:", error)
            return []
        }
    }
}

// MARK: - View Model

@MainActor
class HotelViewModel: ObservableObject {
    @Published var hotels: [Hotel] = []
    @Published var additionalInfo: [AdditionalInfo] = []
    @Published var isLoading: Bool = false
    @Published var errorMessage: String?

    private var tasks: [Task<Void, Never>] = []

    // Fetch data from NetworkService
    func fetchData() {
        guard #available(iOS 15.0, *) else { return }

        isLoading = true
        cancelTasks() // Cancel any existing tasks

        // Task to fetch hotels
        let fetchHotelsTask = Task {
            do {
                let hotels = try await NetworkService.shared.fetchHotels()
                self.hotels = hotels
            } catch {
                if !Task.isCancelled {
                    self.errorMessage = "Failed to fetch hotels: \(error.localizedDescription)"
                }
            }
        }

        // Task to fetch additional info
        let fetchAdditionalInfoTask = Task {
            do {
                let additionalInfo = try await NetworkService.shared.fetchAdditionalInfo()
                self.additionalInfo = additionalInfo
            } catch {
                if !Task.isCancelled {
                    self.errorMessage = "Failed to fetch additional info: \(error.localizedDescription)"
                }
            }
        }

        // Store tasks for cancellation and completion handling
        tasks = [fetchHotelsTask, fetchAdditionalInfoTask]

        // Wait for both tasks to complete before setting isLoading to false
        Task {
            await fetchHotelsTask.value
            await fetchAdditionalInfoTask.value
            self.isLoading = false
        }
    }

    // Cancel all ongoing tasks
    func cancelTasks() {
        tasks.forEach { $0.cancel() }
        tasks.removeAll()
    }
}

// MARK: - Content View

struct ContentView: View {
    @StateObject private var viewModel = HotelViewModel()

    var body: some View {
        NavigationView {
            VStack {
                // Show loading indicator while fetching data
                if viewModel.isLoading {
                    ProgressView("Loading...")
                        .progressViewStyle(CircularProgressViewStyle())
                        .onDisappear {
                            viewModel.cancelTasks() // Cancel the tasks if the view disappears
                        }
                }
                // Show error message if data fetching fails
                else if let errorMessage = viewModel.errorMessage {
                    Text(errorMessage)
                        .foregroundColor(.red)
                }
                // Display fetched additional info and hotels list
                else {
                    Text(viewModel.additionalInfo.first?.title ?? "No additional info")
                        .font(.headline)
                        .padding()
                    
                    List(viewModel.hotels) { hotel in
                        VStack(alignment: .leading) {
                            Text(hotel.name)
                                .font(.headline)
                            Text(hotel.cuisine)
                                .font(.subheadline)
                        }
                    }
                    .listStyle(InsetGroupedListStyle())
                }
            }
            .navigationTitle("Hotels")
            .onAppear {
                viewModel.fetchData() // Fetch data when view appears
            }
            .onDisappear {
                viewModel.cancelTasks() // Cancel tasks when view disappears
            }
        }
    }
}

// MARK: - App Entry Point

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Leave a Comment

Your email address will not be published. Required fields are marked *