Home > Software design >  Switch between making API call, and setting mocked data without an API call in SWIFT
Switch between making API call, and setting mocked data without an API call in SWIFT

Time:08-09

During the development of a swiftUI project, I wish to have option to switch the way data is set, either

  1. Allowing API calls to be made as normal which sets an @Published variable, or
  2. Set the @published variable from mocked file and NOT make the api call.

The reason is that I am limited to the number of api calls per minute.

In my example below I load the mocked data in a model called "Person".

Current solution

  • Set a global variable to distinguish between the two above mentioned states.
  • In all places where api calls were be made, I introduce a condition to optionally use mocked data and not make the api call. See .task in MyView
struct GlobalConstants {
    static let use_mock_data = true
}
class ViewModel: ObservableObject {
    @Published var data: [Person] = []
    
    @MainActor
    func fetchData() async {
        // ... data is set in this code
    }
    
}

Within Person model, I set a static variable that returns the decoded mock data from a json file. The decode method below is an extension to Bundle (Thanks Paul Hudsen).

extension Person {
    static var mockPersons: [Person] {
        Bundle.main.decode([Person].self, from: "persons.json")
    }
    
}

struct MyView: View {

    @StateObject var vm = PersonViewModel()
    
    
    var body: some View {
        NavigationView {
            List {
                ForEach(vm.data) { d in
                    NavigationLink {
                        OtherView(prop: d.detail)
                    } label: {
                     Text(d.name)
                    }
                }
            }
            .task {   // -----condition--------------------- //
                if GlobalConstants.use_mock_data {
                    vm.data = Person.mockPersons
                } else {
                    await vm.fetchData()
                }
            }

        }
    }
}

Question

What other approaches can I consider for enabling the two states? Overriding the methods in some way?

I am still on the learning curve to swift and wondering if theres a better way to enable long term maintenance in a clean and predictable way.

CodePudding user response:

As fetching the data belongs to the view model my suggestion is to put the condition into fetchData.

Marking a method as async doesn't require necessarily that the executed code is asynchronous

@MainActor
class ViewModel: ObservableObject {
    @Published var people: [Person] = []
           
    func fetchData() async {
        if GlobalConstants.use_mock_data {
            people = Person.mockPersons
        } else {
            // people = await callTheAPI()
        }
    }   
}

and replace the task in the view with

.task {  
     await vm.fetchData()
}

Note: people is a more meaningful name than data

  • Related