Home > Software engineering >  Swift get data from service in view model
Swift get data from service in view model

Time:02-16

Hi I'm a beginner at swift and I'm using a ViewModel and I'm trying to fetch data from an api I've setup locally as follows;

//
//  LocationsViewModel.swift
//
//

import Foundation
import MapKit
import SwiftUI

class LocationsViewModel: ObservableObject {
    
    // all loaded locations
    @Published var locations: [Location]
    
    // Current location on map

    @Published var mapLocation: Location {
        didSet {
            updateMapRegion(location: mapLocation)
        }
    }
    
    // current region on map
    @Published var mapRegion: MKCoordinateRegion = MKCoordinateRegion()
    
    // show list of locations
    @Published var showLocationsList: Bool = false
    
    
    // show location detail via sheet
    @Published var sheetLocation: Location? = nil
    
    let mapSpan = MKCoordinateSpan(
        latitudeDelta: 0.1,
        longitudeDelta: 0.1
    )
    
    init() {
     LocationsDataService.fetch()
    let locations = LocationsDataService.locations
    self.locations = locations
    self.mapLocation = locations.first!

    self.updateMapRegion(location: locations.first!)
        
    }
    
    
    private func updateMapRegion(location: Location) {
        withAnimation(.easeInOut) {
            mapRegion = MKCoordinateRegion(
                center: location.coordinates,
                span: mapSpan
            )
        }
    }
    
    func toggleLocationsList() {
        withAnimation(.spring()) {
            showLocationsList.toggle()
        }
    }
    
    func showNextLocation(location: Location) {
        withAnimation(.easeInOut) {
            mapLocation = location
            showLocationsList = false
        }
    }
    

}

However this is throwing me an error on the following two lines;

    LocationsDataService.fetch()
    let locations = LocationsDataService.locations

Which are;

Instance member 'fetch' cannot be used on type 'LocationsDataService'; did you mean to use a value of this type instead?

Instance member 'locations' cannot be used on type 'LocationsDataService'; did you mean to use a value of this type instead?

My data service is as follows;

//
//  LocationsDataService.swift
//

import Foundation
import MapKit

class LocationsDataService {
    let token = "2|asOnUG27uCrcVuGxOO65kS25mX0nUSls5ApfemQy";
    @Published var locations: [Location] = []
    
    func fetch() {
        guard let url = URL(string: "http://localhost:8080/api/locations") else {
            print("Invalid url...")
            return
        }
        var urlRequest = URLRequest( //2
            url: url
        )
        urlRequest.setValue( //3
            "Bearer \(token)",
            forHTTPHeaderField: "Authentication"
        )
        urlRequest.setValue( //4
            "application/json;charset=utf-8",
            forHTTPHeaderField: "Content-Type"
        )
        
        let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] data, _, error in
            guard let data = data, error == nil else {
                return
            }

            do {
                let apiLocations = try JSONDecoder().decode([Location].self, from: data)
                DispatchQueue.main.async {
                    self?.locations = apiLocations
                }
            } catch {
                print(error)
            }
        
        }
        task.resume()
        
    }
    
}

This is my location model;

//
//  Location.swift
//
//

import Foundation
import MapKit
import CoreLocation

struct Location: Identifiable, Equatable, Codable {
    
    static func == (lhs: Location, rhs: Location) -> Bool {
        return lhs.id == rhs.id
    }
    
    let name: String
    let cityName: String
//    let coordinates: CLLocationCoordinate2D
    let description: String
    let imageNames: [String]
    let link: String
    var id: String {
        // create computed variable for id
        name   cityName
    }
    let latitude: Double
    let longitude: Double

    var coordinates: CLLocationCoordinate2D {
        return CLLocationCoordinate2D(latitude: CLLocationDegrees(latitude), longitude: CLLocationDegrees(longitude))
    }
}

Any ideas how I can fetch the data from the api in my service class and use it in the LocationsViewModel??

CodePudding user response:

By calling LocationsDataService.fetch() you are calling static function fetch which doesn't exist. You need an instance of LocationsDataService, so basically you need to instantiate it and then call fetch() like this

let dataService = LocationsDataService()
dataService.fetch()

It is the same story with locations property.

Otherwise you can make fetch and locations static on LocationsDataService but usually that is something you don't want.

CodePudding user response:

There are several little mistakes in your code. The First one is that you are accessing fetch() and locations as if they are are static variables. They are not, you need an instance of the service, something like

let service = LocationsDataService()

That would be put at class level. But this won't work as you expect...

fetch() is asynchronous so locations will not have a value immediately. Because of your usage I suggest you use a "completion handler" and make your service a struct instead of a class

https://docs.swift.org/swift-book/LanguageGuide/Closures.html

That being said, this is why...

An @Published only works inside of an ObservableObject it can't just be a class.

You can't chain ObservableObjects and expect to get an update by itself. You would need to subscribe using .sink in order to get updates in the view model.

But you aren't getting several updates so you don't need to add the subscription a completion handler would give you the one time result.

  • Related