1

I'm using Apollo iOS to fetch GraphQL queries. I want to move the apollo.fetch() query closures to a separate function in a class. This class will contain a static reference to the apollo client as well as functions for performing GraphQL mutations and queries.

I'm trying the following:

static func fetchQueryResults() -> CountriesQuery.Data.Country?{
    var myResult: CountriesQuery.Data.Country?
    myResult = nil
    apollo.fetch(query: countriesQuery) { (result, error) in
        print(result?.data)
        myResult = result?.data //this line causes error
    }
    return myResult
}

Whenever I add the line myResult = result?.data I get the error Generic parameter 'Query' could not be inferred.

However, when the line is commented out it works fine, but obviously the function is pointless. Eventually I would like to generalize this function so I could pass the query into it, but how do I get the data out of this basic closure?

In essence the question is, how do I "wrap" a closure in a function?

The point of this function is to be able to get the number of rows for the table view section in the function:

override func tableView(_ tableView:UITableView, numberOfRowsInSection section: Int -> Int{
    return fetchQueryResults.count
}

However, the view loads before this function runs. I think this is because the apollo.fetch() is running asynchronously?

ryanmattscott
  • 321
  • 5
  • 17
  • I am confused... your closure takes "result" as a parameter and you use "results" – Naresh Mar 22 '19 at 05:45
  • @Naresh typo in post, just fixed it – ryanmattscott Mar 22 '19 at 06:04
  • not sure about that error, but I would suggest you to not use the function as a datasource... keep an array... I know what your confusion is, the ingredient you are missing is called an escaping closure... go here to understand it - https://medium.com/@bestiosdevelope/what-do-mean-escaping-and-nonescaping-closures-in-swift-d404d721f39d it will take some time to understand it, depends on your experience... after you implement the closure, your fetchQueryResults won't return anything but it will provide an array as input to the closure which will refresh the tableview etc... – Naresh Mar 22 '19 at 06:42

2 Answers2

2

Currently I'm using Apollo iOS version 0.16.0 which is the latest release https://github.com/apollographql/apollo-ios

There are a few changes related to version lower than 0.13.0, you can take a look at 0.13.0 release notes - Don't use (result, error) because they've switched from a tuple of nullable parameters to a Result.

Try to use something like this:

Apollo.shared.client.fetch(query: GetProductQuery(id: id)) { results in

        do {

            if let gqlErrors = try results.get().errors {
                if(!gqlErrors.isEmpty) {
                    apiResponseError.message = gqlErrors[0].message
                    publishSubject.onError(apiResponseError)
                }
                return
            }

            guard let gplResult = try results.get().data?.getProduct else {
                apiResponseError.message = "Error getting results"
                publishSubject.onError(apiResponseError)
                return
            }

            publishSubject.onNext(gplResult)

        } catch let errorException {
            apiResponseError.message = errorException.localizedDescription
            publishSubject.onError(apiResponseError)
        }

    }
1

Whenever I add the line myResult = result?.data I get the error Generic parameter 'Query' could not be inferred.

A couple things that might fix it:

  1. Add import Apollo to that Swift file. You probably already have it, but if you create your apollo instance using a static utility class, that could be possible.

  2. Drop the , error in your closure so it's just { result in. I think that's older syntax that's in a lot of tutorials out there, but the error is now part of the result itself.

However, the view loads before this function runs. I think this is because the apollo.fetch() is running asynchronously?

By default, it runs DispatchQueue.main, but you can use the fetch() function call and fill out which DispatchQueue you want it to run on (i.e. as you type "fetch" in xCode, autocomplete will actually show it as 2 different func signatures: one that hides all the params with default values, and a second one where you can explicitly fill out each param).

In general, a good pattern for async loading data in a table view is:

var countries: [Country] = [Country]()

func viewDidLoad() {
    super.viewDidLoad()
    apollo.fetch(query: countriesQuery) { result in
        self.countries.removeAll()  // clear the old data
        // unpack each Country from result
        // add to countries array
        self.tableView.reloadData()  // tell the table view to refresh
    }
}

override func tableView(_ tableView:UITableView, numberOfRowsInSection section: Int -> Int{
    return countries.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let country = countries[indexPath.row]
    let yourCell: YourCell = tableView.dequeueReusableCell(withIdentifier: "yourCellKey", for: indexPath) as! YourCell
    yourCell.setCountry(country)
    return yourCell
}
wildcat12
  • 975
  • 6
  • 13